package org.jabref.logic.util.io;

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.HexFormat;
import java.util.Optional;

import org.jabref.logic.util.BackupFileType;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class BackupFileUtil {

    private static final Logger LOGGER = LoggerFactory.getLogger(BackupFileUtil.class);

    private BackupFileUtil() {
    }

    /// Determines the path of the backup file (using the given extension)
    ///
    /// As default, a directory inside the user temporary dir is used.
    /// In case a AUTOSAVE backup is requested, a timestamp is added
    ///
    ///
    /// _SIDE EFFECT_: Creates the directory.
    /// In case that fails, the return path of the .bak file is set to be next to the .bib file
    ///
    /// Note that this backup is different from the `.sav` file generated by `org.jabref.gui.autosaveandbackup.BackupManager`
    /// (and configured in the preferences as "make backups")
    public static Path getPathForNewBackupFileAndCreateDirectory(Path targetFile, BackupFileType fileType, Path backupDir) {
        String extension = "." + fileType.getExtensions().getFirst();
        String timeSuffix = ZonedDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd--HH.mm.ss"));

        // We choose the data directory, because a ".bak" file should survive cache cleanups
        Path directory = backupDir;
        try {
            Files.createDirectories(directory);
        } catch (IOException e) {
            Path result = FileUtil.addExtension(targetFile, extension);
            LOGGER.warn("Could not create bib writing directory {}, using {} as file", directory, result, e);
            return result;
        }
        String baseFileName = getUniqueFilePrefix(targetFile) + "--" + targetFile.getFileName() + "--" + timeSuffix;
        Path fileName = FileUtil.addExtension(Path.of(baseFileName), extension);
        return directory.resolve(fileName);
    }

    public static Optional<Path> getPathOfLatestExistingBackupFile(Path targetFile, BackupFileType fileType, Path backupDir) {
        // The code is similar to "getPathForNewBackupFileAndCreateDirectory"

        String extension = "." + fileType.getExtensions().getFirst();

        if (Files.notExists(backupDir)) {
            // In case there is no app directory, we search in the directory of the bib file
            Path result = FileUtil.addExtension(targetFile, extension);
            if (Files.exists(result)) {
                return Optional.of(result);
            } else {
                return Optional.empty();
            }
        }

        // Search the directory for the latest file
        final String prefix = getUniqueFilePrefix(targetFile) + "--" + targetFile.getFileName();
        Optional<Path> mostRecentFile;
        try {
            mostRecentFile = Files.list(backupDir)
                                  // just list the .sav belonging to the given targetFile
                                  .filter(p -> p.getFileName().toString().startsWith(prefix))
                                  .sorted()
                                  .reduce((first, second) -> second);
        } catch (IOException e) {
            LOGGER.error("Could not determine most recent file", e);
            return Optional.empty();
        }
        return mostRecentFile;
    }

    /**
     * <p>
     * Determines a unique file prefix.
     * </p>
     * <p>
     * When creating a backup file, the backup file should belong to the original file.
     * Just adding ".bak" suffix to the filename, does not work in all cases:
     * It may be possible that the user has opened "paper.bib" twice.
     * Thus, we need to create a unique prefix to distinguish these files.
     * </p>
     */
    public static String getUniqueFilePrefix(Path targetFile) {
        // Idea: use the hash code and convert it to hex
        // Thereby, use positive values only and use length 4
        int positiveCode = Math.abs(targetFile.hashCode());
        byte[] array = ByteBuffer.allocate(4).putInt(positiveCode).array();
        return HexFormat.of().formatHex(array);
    }
}
